﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Collections.ObjectModel;
using System.Xml.Serialization;
using System.IO;
using System.Diagnostics;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using SharpSvn;
using System.Text.RegularExpressions;
using SharpSvn.Implementation;
using System.Windows.Threading;

namespace SvnToTfs
{
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window
  {

    #region Private variables

    private TFSModule _tfsMod;
    private SvnHelper _svnHelper;
    private Dictionary<string, Object> _directoryStructure;
    private Action<SvnMigrationSettingsEntity> _migrationHandler = null;

    #endregion

    #region Constructors

    public MainWindow()
    {
      InitializeComponent();
    }

    #endregion

    #region Public methods

    public static void AppendInLogFile(string line)
    {
      using (StreamWriter sw = File.AppendText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log.txt")))
      {
        sw.Write(line);
      }
    }

    #endregion

    #region Private methods

    private void _btnStart_Click(object sender, RoutedEventArgs e)
    {
      if (_migrationHandler == null)
      {
        _btnStart.IsEnabled = false;

        SvnMigrationSettingsEntity migArgs = new SvnMigrationSettingsEntity()
        {
          Filepath = FixTrailingSlash(_txtFilePath.Text),
          SubversionUrl = _txtSubversionUrl.Text,
          Username = _txtUsername.Text,
          Password = _txtPassword.Password,
          StartRevision = GetRevisionNumber(_txtStartRevision.Text, 0),
          EndRevision = GetRevisionNumber(_txtEndRevision.Text, null),
          ProjectCollection = (string)_cboTfsCollection.SelectedItem,
          Project = (string)_cboTfsProject.SelectedItem,
          NewImport = _cbNewImport.IsChecked,
          Branch = (string)_cboProjectBranch.SelectedItem,
        };


        _migrationHandler = StartMigrationAsync;
        _migrationHandler.BeginInvoke(migArgs, null, null);
      }
    }

    /// <summary>
    /// Start the migration
    /// </summary>
    /// <param name="migArgs"></param>
    private void StartMigrationAsync(SvnMigrationSettingsEntity migArgs)
    {

      SetInitialStateUI();

      _svnHelper = new SvnHelper(migArgs.SubversionUrl);
      _directoryStructure = new Dictionary<string, object>();

      // Create our SVN Client and authenticate with the supplied username/password
      SvnClient c = new SvnClient();
      c.Authentication.DefaultCredentials = new System.Net.NetworkCredential(migArgs.Username, migArgs.Password);
      Uri u = new Uri(migArgs.SubversionUrl);
      var target = SvnTarget.FromUri(u);

      EnsureEndRevision(migArgs, c, target);

      List<SvnLogEventArgs> logList;

      // Get the list of revisions/logs
      if (migArgs.NewImport == true)
        logList = GetSvnLogs(migArgs, c, u);
      else
        logList = GetAllSvnLogs(migArgs, c, u);

      Stopwatch timer = new Stopwatch();
      timer.Start();

      // Perform the actual import
      if (logList.Count > 0)
        PerformSvnCheckoutUpdate(migArgs, c, target, logList);

      timer.Stop();

      // We're done with the import
      SetFinalStateUI(timer);
    }

    /// <summary>
    /// If no end revision was supplied, find the HEAD and use it
    /// as the end revision
    /// </summary>
    /// <param name="migArgs"></param>
    /// <param name="c"></param>
    /// <param name="target"></param>
    private void EnsureEndRevision(SvnMigrationSettingsEntity migArgs, SvnClient c, SvnTarget target)
    {
      if (migArgs.EndRevision == null)
      {
        DisplayMessage(SvnToTfsResources.NoEndRevision);

        SvnInfoEventArgs ea;
        c.GetInfo(target, out ea);
        migArgs.EndRevision = ea.Revision;
        string msg = string.Format(SvnToTfsResources.FoundHeadRevision, migArgs.EndRevision.ToString());
        DisplayMessage(msg);
      }
    }

    /// <summary>
    /// Returns a list of revisions and logs for a SVN repository
    /// </summary>
    /// <param name="migArgs"></param>
    /// <param name="c"></param>
    /// <param name="u"></param>
    /// <returns></returns>
    private List<SvnLogEventArgs> GetSvnLogs(SvnMigrationSettingsEntity migArgs, SvnClient c, Uri u)
    {
      DisplayMessage(SvnToTfsResources.QueryingRevision);

      List<SvnLogEventArgs> logList;

      try
      {
        Collection<SvnLogEventArgs> logs;

        //Get the list of SVN revisions
        c.GetLog(u, new SvnLogArgs()
        {
          Range = new SvnRevisionRange(migArgs.StartRevision, migArgs.EndRevision),
          StrictNodeHistory = true,
          RetrieveMergedRevisions = true
        }, out logs);

        Dispatcher.BeginInvoke((Action)(() =>
        {
          _pgbMain.Value = 1;
          _pgbExportImport.Maximum = logs.Count;
          _txtStatusProgressionMax.Text = logs.Count.ToString();
          string msg = string.Format(SvnToTfsResources.FoundLogs, logs.Count.ToString());
          AppendText(msg);
        }));

        long svnStartRevision = logs[0].Revision;
        migArgs.StartRevision = migArgs.StartRevision < svnStartRevision ? svnStartRevision : migArgs.StartRevision;

        logList = new List<SvnLogEventArgs>(logs);
      }
      catch (Exception)
      {
        DisplayMessage(SvnToTfsResources.ErrorRevisionLogs);
        logList = new List<SvnLogEventArgs>();
      }

      return logList;
    }

    /// <summary>
    /// Returns a list of revisions and logs for a SVN repository
    /// </summary>
    /// <param name="migArgs"></param>
    /// <param name="c"></param>
    /// <param name="u"></param>
    /// <returns></returns>
    private List<SvnLogEventArgs> GetAllSvnLogs(SvnMigrationSettingsEntity migArgs, SvnClient c, Uri u)
    {
      DisplayMessage(SvnToTfsResources.QueryingRevisionLogs);

      List<SvnLogEventArgs> logList;

      try
      {
        Collection<SvnLogEventArgs> logs;

        //Get all SVN logs from the first revision to the specified revision
        c.GetLog(u, new SvnLogArgs()
        {
          Range = new SvnRevisionRange(1, migArgs.EndRevision),
          StrictNodeHistory = true,
          RetrieveMergedRevisions = true
        }, out logs);

        Dispatcher.BeginInvoke((Action)(() =>
        {
          _pgbMain.Value = 1;
          _pgbExportImport.Maximum = logs.Count;
          _txtStatusProgressionMax.Text = logs.Count.ToString();
          string msg = string.Format(SvnToTfsResources.FoundLogs, logs.Count);
          AppendText(msg);
        }));

        long svnStartRevision = logs[0].Revision;
        migArgs.StartRevision = migArgs.StartRevision < svnStartRevision ? svnStartRevision : migArgs.StartRevision;

        logList = new List<SvnLogEventArgs>(logs);
      }
      catch (Exception)
      {
        DisplayMessage(SvnToTfsResources.ErrorFetchingRevisionLogs);
        logList = new List<SvnLogEventArgs>();
      }

      return logList;
    }


    /// <summary>
    /// Performs the import using a "SVN Checkout" and then multiple updates
    /// </summary>
    /// <param name="migArgs"></param>
    /// <param name="c"></param>
    /// <param name="target"></param>
    /// <param name="logList"></param>
    private void PerformSvnExport(SvnMigrationSettingsEntity migArgs, SvnClient c, SvnTarget target, List<SvnLogEventArgs> logList)
    {
      if (Directory.Exists(migArgs.Filepath))
      {
        Directory.Delete(migArgs.Filepath, true);
      }

      DisplayMessage(SvnToTfsResources.SubversionExportStart);

      // Initiate the connection to the TFS Server
      _tfsMod.InitiateTFS(migArgs.ProjectCollection, migArgs.Project, migArgs.Branch, migArgs.Filepath);

      Stopwatch timer = new Stopwatch();
      timer.Start();

      SvnUpdateResult svnr = null;

      //Perform the initial "SVN checkout" to the TFS local working folder
      c.Export(new SvnUriTarget(migArgs.SubversionUrl), migArgs.Filepath,
                                                      new SvnExportArgs()
                                                      {
                                                        Revision = logList[0].Revision,
                                                        Depth = SvnDepth.Infinity,
                                                        Overwrite = true
                                                      }, out svnr);

      GenerateDirectoryStructure(_directoryStructure, migArgs.Filepath);

      timer.Stop();
      SvnTimeLog(logList[0], timer);

      int logIndex = 1;

      if (migArgs.NewImport == true)
        PerformTFSInitialExport(migArgs, logList[0]);
      else
        logIndex = BuildStructureUpToRevision(_directoryStructure, logList, migArgs.StartRevision);

      foreach (var log in logList.GetRange(logIndex, logList.Count - logIndex))
      {
        if (log.Revision >= migArgs.StartRevision && log.Revision <= migArgs.EndRevision)
        {
          timer = new Stopwatch();
          timer.Start();

          //Perform the initial "SVN checkout" to the TFS local working folder
          c.Export(new SvnUriTarget(migArgs.SubversionUrl), migArgs.Filepath,
                                  new SvnExportArgs()
                                  {
                                    Revision = log.Revision,
                                    Depth = SvnDepth.Infinity,
                                    Overwrite = true
                                  }, out svnr);


          timer.Stop();
          SvnTimeLog(log, timer);
          PerformExportToTFS(false, migArgs, log);
        }
        else
        {
          string msg = string.Format(SvnToTfsResources.SkippedRevision, log.Revision.ToString(), log.LogMessage);
          DisplayMessage(msg);
        }

        ++logIndex;

        Dispatcher.BeginInvoke((Action<long, SvnLogEventArgs>)((i, l) =>
        {
          _pgbExportImport.Value = i;
          _txtStatusProgressionCurrent.Text = i.ToString();
          _txtStatusLogMessage.Text = l.LogMessage;
          _txtStatusRevision.Text = l.Revision.ToString();
        }), logIndex, log);
      }
    }

    /// <summary>
    /// Performs the import using a "SVN Checkout" and then multiple updates
    /// </summary>
    /// <param name="migArgs"></param>
    /// <param name="c"></param>
    /// <param name="target"></param>
    /// <param name="logList"></param>
    private void PerformSvnCheckoutUpdate(SvnMigrationSettingsEntity migArgs, SvnClient c, SvnTarget target, List<SvnLogEventArgs> logList)
    {
      string msg;
      // Inform that the directory exists
      if (Directory.Exists(migArgs.Filepath))
      {
        msg = string.Format(SvnToTfsResources.DirectoryExist, migArgs.Filepath);
        DisplayMessage(msg);

        DeleteDirectory(migArgs.Filepath);

        DisplayMessage(SvnToTfsResources.DeleteDirectory);
      }

      DisplayMessage(SvnToTfsResources.CheckoutStart);

      // Initiate the connection to the TFS Server
      _tfsMod.InitiateTFS(migArgs.ProjectCollection, migArgs.Project, migArgs.Branch, migArgs.Filepath);

      Stopwatch timer = new Stopwatch();
      timer.Start();

      // Perform the initial checkout of the repository
      SvnUpdateResult svnr = null;

      c.Export(target, migArgs.Filepath, new SvnExportArgs()
      {
        Revision = logList[0].Revision,
        Overwrite = true
      }, out svnr);

      GenerateDirectoryStructure(_directoryStructure, migArgs.Filepath);

      timer.Stop();
      SvnTimeLog(logList[0], timer);

      int logIndex = 1;

      if (migArgs.NewImport == true)
        PerformTFSInitialExport(migArgs, logList[0]);
      else
        logIndex = BuildStructureUpToRevision(_directoryStructure, logList, migArgs.StartRevision);

      bool nextIsCheckout = true;

      foreach (var log in logList.GetRange(1, logList.Count - 1))
      {
        if (log.Revision >= migArgs.StartRevision && log.Revision <= migArgs.EndRevision)
        {
          timer = new Stopwatch();
          timer.Start();

          try
          {
            if (nextIsCheckout)
            {
              DeleteDirectory(migArgs.Filepath);

              //Perform a "SVN checkout" to the TFS local working folder
              c.CheckOut(new SvnUriTarget(migArgs.SubversionUrl), migArgs.Filepath, new SvnCheckOutArgs()
              {
                Revision = log.Revision,
                Depth = SvnDepth.Infinity,
                AllowObstructions = true
              }, out svnr);

              nextIsCheckout = false;
            }
            else
            {
              //Perform a "SVN update" on the revision
              c.Update(migArgs.Filepath, new SvnUpdateArgs()
              {
                Revision = log.Revision,
                Depth = SvnDepth.Infinity,
                AllowObstructions = true,
              }
              , out svnr);
            }
          }
          catch (SvnException e)
          {
            DisplayMessage(String.Format(SvnToTfsResources.ExportInsteadUpdate, log.Revision.ToString()));
            DisplayMessage(String.Format(SvnToTfsResources.SVNError, e.Message));
            DeleteDirectory(migArgs.Filepath);

            // Perform an export of the the revision
            c.Export(target, migArgs.Filepath, new SvnExportArgs()
            {
              Revision = log.Revision,
              Overwrite = true
            }, out svnr);

            nextIsCheckout = true;
          }

          timer.Stop();
          SvnTimeLog(log, timer);
          PerformExportToTFS(false, migArgs, log);
        }
        else
        {
          msg = string.Format(SvnToTfsResources.SkippedRevision, log.Revision.ToString(), log.LogMessage);
          DisplayMessage(msg);
        }

        ++logIndex;

        Dispatcher.BeginInvoke((Action<long, SvnLogEventArgs>)((i, l) =>
        {
          _pgbExportImport.Value = i;
          _txtStatusProgressionCurrent.Text = i.ToString();
          _txtStatusLogMessage.Text = l.LogMessage;
          _txtStatusRevision.Text = l.Revision.ToString();
        }), logIndex, log);
      }
    }


    /// <summary>
    /// Builds the directory structure in memory up to the specified revision
    /// </summary>
    /// <param name="_structure"></param>
    /// <param name="logList"></param>
    /// <param name="startRevision"></param>
    /// <returns></returns>
    private int BuildStructureUpToRevision(Dictionary<string, object> _structure, List<SvnLogEventArgs> logList, long? startRevision)
    {
      int index = 1;

      foreach (SvnLogEventArgs revision in logList.GetRange(index, logList.Count - 1))
      {
        if (revision.Revision >= startRevision)
          return index;

        foreach (SvnChangeItem changeItem in revision.ChangedPaths)
        {
          SvnChangeAction action = changeItem.Action;

          switch (action)
          {
            case SvnChangeAction.Add:
              AddDirectory(_structure, _svnHelper.ConvertUrlToLocal(changeItem.Path)); break;
            case SvnChangeAction.Delete:
              RemoveDirectory(_structure, _svnHelper.ConvertUrlToLocal(changeItem.Path)); break;
          }
        }

        ++index;
      }

      return index;
    }

    /// <summary>
    /// Perform the initial export into TFS
    /// </summary>
    /// <param name="migArgs"></param>
    /// <param name="revision"></param>
    private void PerformTFSInitialExport(SvnMigrationSettingsEntity migArgs, SvnLogEventArgs revision)
    {

      Stopwatch timer = new Stopwatch();
      timer.Start();
    
      DisplayMessage(string.Format(SvnToTfsResources.InitialTFSRevision, revision.Revision.ToString()));


      RecursiveAddFiles(migArgs.Filepath);


      // Now check in the pending changes if there's any
      var pending = new List<PendingChange>((IEnumerable<PendingChange>)
              _tfsMod.Workspace.GetPendingChanges());

      int success = 0;
      if (pending.Count > 0)
        //Perform the initial TFS "Checkin"
        success = _tfsMod.Workspace.CheckIn(pending.ToArray(), revision.LogMessage);

      timer.Stop();

      Dispatcher.BeginInvoke((Action<SvnLogEventArgs, Stopwatch>)((l, t) =>
      {
        string time = (t.ElapsedMilliseconds / 1000.0).ToString("0.0");
        AppendText(string.Format(SvnToTfsResources.TFSImportDone, revision.Revision.ToString(), time));
      }), revision, timer);

    }


    /// <summary>
    /// Recursively add files/folders in a folder to TFS
    /// </summary>
    /// <param name="directory"></param>
    private void RecursiveAddFiles(string directory)
    {

      foreach (string file in Directory.GetFiles(directory))
        _tfsMod.Workspace.PendAdd(file);

      foreach (string subDirectory in Directory.GetDirectories(directory))
      {
        _tfsMod.Workspace.PendAdd(subDirectory);

        RecursiveAddFiles(subDirectory);
      }
    }

    /// <summary>
    /// Perform the export of a SVN revision into TFS
    /// </summary>
    /// <param name="migArgs"></param>
    /// <param name="revision"></param>
    /// <param name="ws"></param>
    private void PerformExportToTFS(bool initialExport, SvnMigrationSettingsEntity migArgs, SvnLogEventArgs revision)
    {
      Stopwatch timer = new Stopwatch();
      timer.Start();

      DisplayMessage(string.Format(SvnToTfsResources.TFSRevision, revision.Revision.ToString()));

      bool ignoreNext = false;
      SvnChangeItem changeItem;

      // Now go through each change in the SVN for this revision
      for (int i = 0; i < revision.ChangedPaths.Count; ++i)
      {
        changeItem = revision.ChangedPaths[i];

        // Validates the path belong to repository section the user requested
        if (_svnHelper.ValidateItemAgainstUrl(changeItem.Path) && !ignoreNext)
        {
          SvnChangeAction action = initialExport == true ? SvnChangeAction.Add : changeItem.Action;
          string filePath = migArgs.Filepath;

          // Convert the absolute path to a local relative path so we can do a direct mapping
          // with the local directory
          string localPath = renameFileIfNecessary(filePath + _svnHelper.ConvertUrlToLocal(changeItem.Path));

          // Check for rename that only affect case
          if (i + 1 <= revision.ChangedPaths.Count - 1)
          {
            if (OnlyCaseDiffer(changeItem, revision.ChangedPaths[i + 1]))
            {
              string oldPath = filePath + _svnHelper.ConvertUrlToLocal(revision.ChangedPaths[i + 1].Path);

              _tfsMod.Workspace.PendRename(oldPath, localPath);
              ignoreNext = true;
            }
          }

          if (!ignoreNext)
          {
            string localUrl = TrimChar(_svnHelper.ConvertUrlToLocal(changeItem.Path));

            //Check the SvnChangeAction (for folders)
            switch (action)
            {
              case (SvnChangeAction.Add):
                AddDirectory(_directoryStructure, localUrl); break;
              case (SvnChangeAction.Delete):
                if (DuplicatePathExists(_directoryStructure, localUrl))
                {
                  _tfsMod.Workspace.PendRename(filePath + localUrl, filePath + GetDuplicateDirectoryName(_directoryStructure, localUrl));
                  action = SvnChangeAction.None;
                }
                RemoveDirectory(_directoryStructure, _svnHelper.ConvertUrlToLocal(changeItem.Path));
                break;

              default: break;
            }
          }

          if (!ignoreNext)
            TakeAppropriateTFSAction(action, localPath);

        }
        else
        {
          ignoreNext = false;
        }
      }

      // Now check in the pending changes if there's any
      var pending = new List<PendingChange>((IEnumerable<PendingChange>)
              _tfsMod.Workspace.GetPendingChanges());

      int success = 0;
      if (pending.Count > 0)
      {
        //Perform the TFS "Checkin"
        success = _tfsMod.Workspace.CheckIn(pending.ToArray(), string.Format(SvnToTfsResources.TFSCheckin, revision.Revision.ToString(), revision.LogMessage));
      }
      timer.Stop();

      Dispatcher.BeginInvoke((Action<SvnLogEventArgs, Stopwatch>)((l, t) =>
      {
        string time = (t.ElapsedMilliseconds / 1000.0).ToString("0.0");
        AppendText(string.Format(SvnToTfsResources.TFSImportDone, revision.Revision, time));
      }), revision, timer);
    }

    /// <summary>
    /// Trim the filename from characters that aren't allowed
    /// </summary>
    /// <param name="p"></param>
    /// <returns></returns>
    private string TrimChar(string p)
    {
      if (p.Contains(';'))
        p = p.Remove(p.IndexOf(';'), 1);

      return p;
    }

    /// <summary>
    /// Rename a Filename if contains characters that aren't allowed
    /// </summary>
    /// <param name="p"></param>
    /// <returns></returns>
    private string renameFileIfNecessary(string p)
    {
      if (p.Contains(';'))
      {
        FileAttributes attr = File.GetAttributes(p);

        if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
        {
          // This is a directory
          DirectoryInfo dir = new DirectoryInfo(p);

          p = p.Remove(p.IndexOf(';'), 1);

          if (dir.Exists && !Directory.Exists(p))
            dir.MoveTo(p);
        }
        else
        {
          FileInfo file = new FileInfo(p);

          if (!file.Name.Contains(';'))
          {
            // We face the situation where the current file doesn't contain
            // an illegal character, but the parent directory does. We have to make sure the
            // parent directory has been created with the correct name

            string correctPath = file.DirectoryName.Remove(p.IndexOf(';'), 1);

            if (!Directory.Exists(correctPath))
            {
              // We have to create the directory with the correct path
              Directory.CreateDirectory(correctPath);
            }
          }

          p = p.Remove(p.IndexOf(';'), 1);

          if (file.Exists)
            file.MoveTo(p);
        }

      }

      return p;
    }

    private bool IsDirectory(string path)
    {
      Regex fileRegex = new Regex("(.*)\\.(.*)");

      if (!fileRegex.IsMatch(path)) return true;

      return false;
    }

    #region Helper methods

    /// <summary>
    /// Initial configuration of the UI
    /// </summary>
    private void SetInitialStateUI()
    {
        Dispatcher.BeginInvoke((Action)(() =>
        {
            _pgbMain.Minimum = 0;
            _pgbMain.Value = 0;
            _pgbMain.Maximum = 2;

            _pgbExportImport.Minimum = 0;
            _pgbExportImport.Value = 0;
            _pgbExportImport.Maximum = 1;

            AppendText(SvnToTfsResources.Start);
        }));
    }

    /// <summary>
    /// Sets the UI correctly when the Export is done 
    /// </summary>
    private void SetFinalStateUI(Stopwatch timer)
    {
        string time = (timer.ElapsedMilliseconds / 1000.0).ToString("0.0");

        Dispatcher.BeginInvoke((Action)(() =>
        {
            _pgbMain.Value = 2;
            _pgbExportImport.Value = _pgbExportImport.Maximum;
            AppendText(string.Format(SvnToTfsResources.ExportDone, time));
            _btnStart.IsEnabled = true;
            _migrationHandler = null;
        }));
    }

    /// <summary>
    /// Takes the right SVN to TFS action
    /// </summary>
    /// <param name="action"></param>
    /// <param name="completePath"></param>
    private void TakeAppropriateTFSAction(SvnChangeAction action, string completePath)
    {
        switch (action)
        {
            case SvnChangeAction.None: break;
            case SvnChangeAction.Add:
                _tfsMod.Workspace.PendAdd(completePath); break;

            case SvnChangeAction.Delete:
                _tfsMod.Workspace.PendDelete(completePath); break;

            case SvnChangeAction.Modify:
            case SvnChangeAction.Replace:
                _tfsMod.Workspace.PendEdit(completePath); break;
        }
    }

    /// <summary>
    /// Returns true if detect a name change between the current Item and the next Item
    /// </summary>
    /// <param name="currentItem"></param>
    /// <param name="nextItem"></param>
    /// <returns></returns>
    private bool OnlyCaseDiffer(SvnChangeItem currentItem, SvnChangeItem nextItem)
    {
        if (string.Compare(currentItem.Path, nextItem.Path, true) == 0
            && string.Compare(currentItem.Path, nextItem.Path) != 0)
            return true;

        return false;
    }

    /// <summary>
    /// Print a time log message to the UI
    /// </summary>
    /// <param name="log"></param>
    /// <param name="timer"></param>
    private void SvnTimeLog(SvnLogEventArgs log, Stopwatch timer)
    {
        Dispatcher.BeginInvoke((Action<SvnLogEventArgs, Stopwatch>)((l, t) =>
        {
            string time = (t.ElapsedMilliseconds / 1000.0).ToString("0.0");
            string msg = string.Format(SvnToTfsResources.SubversionCheckoutDone, l.Revision.ToString(), time, l.LogMessage);
            AppendText(msg);
        }), log, timer);
    }

    /// <summary>
    /// Append text to the logger
    /// </summary>
    /// <param name="msg"></param>
    private void AppendText(string msg)
    {
        string line = string.Format(SvnToTfsResources.MessageDate, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), msg + Environment.NewLine);

        AppendInLogFile(line);

        _txtLog.AppendText(line);
        _txtLog.ScrollToEnd();
    }

    /// <summary>
    /// Returns the revision number from a string
    /// </summary>
    /// <param name="revisionText"></param>
    /// <param name="defaultValue"></param>
    /// <returns></returns>
    private static long? GetRevisionNumber(string revisionText, long? defaultValue)
    {
        long revision = 0;
        bool b = long.TryParse(revisionText, out revision);
        if (b)
        {
            return revision;
        }

        return defaultValue;
    }

    /// <summary>
    /// Make sure the path ends with a trailing slash
    /// </summary>
    /// <param name="path"></param>
    /// <returns></returns>
    private string FixTrailingSlash(string path)
    {
        if (path.EndsWith("\\"))
            return path;
        else
            return path + "\\";
    }

    /// <summary>
    /// Displays a message in the log
    /// </summary>
    /// <param name="message"></param>
    private void DisplayMessage(string message)
    {
        Dispatcher.BeginInvoke((Action)(() =>
        {
            AppendText(message);
        }));
    }

    private void DeleteDirectory(string path)
    {
        DirectoryInfo dirInfo = new DirectoryInfo(path);
        foreach (DirectoryInfo subDirInfo in
        dirInfo.GetDirectories())
        {
            DeleteDirectory(subDirInfo.FullName);
        }
        foreach (FileInfo fileInfo in dirInfo.GetFiles())
        {
            FileAttributes fileAtts =
            File.GetAttributes(fileInfo.FullName);
            if ((fileAtts & FileAttributes.ReadOnly) != 0)
            {
                File.SetAttributes(fileInfo.FullName,
                fileAtts & ~FileAttributes.ReadOnly);
            }
            File.Delete(fileInfo.FullName);
        }

        if (Directory.Exists(path))
            Directory.Delete(path, true);
    }

    #endregion

    #region Directory structure

    /// <summary>
    /// Generates the initial directory structure
    /// </summary>
    /// <param name="dict"></param>
    /// <param name="directory"></param>
    private void GenerateDirectoryStructure(Dictionary<string, object> dict, string directory)
    {
        foreach (string file in Directory.GetFiles(directory))
            dict.Add(new FileInfo(file).Name, new Dictionary<string, object>());

        foreach (string subDirectory in Directory.GetDirectories(directory))
        {
            string relativeDir = new DirectoryInfo(subDirectory).Name;

            Dictionary<string, object> subDict = new Dictionary<string, object>();
            dict.Add(relativeDir, subDict);

            GenerateDirectoryStructure(subDict, subDirectory);
        }
    }

    /// <summary>
    /// Adds a directory to the directory structure
    /// </summary>
    /// <param name="dict"></param>
    /// <param name="directory"></param>
    private void AddDirectory(Dictionary<string, object> dict, string directory)
    {
        List<string> dirs = SplitDirectory(directory);
        Dictionary<string, object> currentDict = dict;

        foreach (string dir in dirs)
        {
            if (currentDict.ContainsKey(dir))
            {
                currentDict = (Dictionary<string, object>)currentDict[dir];
            }
            else
            {
                currentDict.Add(dir, new Dictionary<string, object>());
                currentDict = (Dictionary<string, object>)currentDict[dir];
            }
        }
    }

    private void AddPath(Dictionary<string, object> dict, string path)
    {
        List<string> files = SplitDirectory(path);
        Dictionary<string, object> currentDict = dict;

        foreach (string file in files)
        {
            if (currentDict.ContainsKey(file))
            {
                currentDict = (Dictionary<string, object>)currentDict[file];
            }
            else
            {
                if (IsDirectory(file))
                {
                    currentDict.Add(file, new Dictionary<string, object>());
                    currentDict = (Dictionary<string, object>)currentDict[file];
                }
                else
                {
                    currentDict.Add(file, file);
                }
            }
        }
    }

    /// <summary>
    /// Removes a directory from the directory structure
    /// </summary>
    /// <param name="dict"></param>
    /// <param name="directory"></param>
    private void RemoveDirectory(Dictionary<string, object> dict, string directory)
    {
        List<string> dirs = SplitDirectory(directory);
        Dictionary<string, object> currentDict = dict;

        int index = 0;
        foreach (string dir in dirs)
        {
            if (currentDict.ContainsKey(dir))
            {
                if (index == dirs.Count - 1)
                    currentDict.Remove(dir);
                else
                    currentDict = (Dictionary<string, object>)currentDict[dir];
            }

            ++index;
        }
    }

    private void RemovePath(Dictionary<string, object> dict, string path)
    {
        List<string> files = SplitDirectory(path);
        Dictionary<string, object> currentDict = dict;

        int index = 0;
        foreach (string file in files)
        {
            if (currentDict.ContainsKey(file))
            {
                if (index == files.Count - 1)
                    currentDict.Remove(file);
                else
                    currentDict = (Dictionary<string, object>)currentDict[file];
            }

            ++index;
        }
    }

    /// <summary>
    /// Returns true if the specified directory exists
    /// </summary>
    /// <param name="dict"></param>
    /// <param name="directory"></param>
    /// <returns></returns>
    private bool DirectoryExist(Dictionary<string, object> dict, string directory)
    {
        List<string> dirs = SplitDirectory(directory);
        Dictionary<string, object> currentDict = dict;

        int index = 0;
        foreach (string dir in dirs)
        {
            if (currentDict.ContainsKey(dir))
            {
                if (index == dirs.Count - 1)
                    return true;

                currentDict = (Dictionary<string, object>)currentDict[dir];
            }

            ++index;
        }

        return false;
    }

    /// Returns true if the specified directory exists
    /// </summary>
    /// <param name="dict"></param>
    /// <param name="directory"></param>
    /// <returns></returns>
    private bool PathExists(Dictionary<string, object> dict, string path)
    {
        List<string> dirs = SplitDirectory(path);
        Dictionary<string, object> currentDict = dict;

        int index = 0;
        foreach (string dir in dirs)
        {
            if (currentDict.ContainsKey(dir))
            {
                if (index == dirs.Count - 1)
                    return true;

                currentDict = (Dictionary<string, object>)currentDict[dir];
            }

            ++index;
        }

        return false;
    }

    /// <summary>
    /// Returns true if a directory with the same name (ignoring case) exists
    /// </summary>
    /// <param name="dict"></param>
    /// <param name="directory"></param>
    /// <returns></returns>
    private bool DuplicateDirectoryExists(Dictionary<string, object> dict, string directory)
    {
        List<string> dirs = SplitDirectory(directory);
        Dictionary<string, object> currentDict = dict;

        int index = 0;
        foreach (string dir in dirs)
        {
            if (currentDict.ContainsKey(dir))
            {
                if (index == dirs.Count - 1)
                {
                    foreach (string key in currentDict.Keys)
                        if (string.Compare(key, dir, true) == 0 && key != dir) return true;
                }

                currentDict = (Dictionary<string, object>)currentDict[dir];
            }

            ++index;
        }

        return false;
    }

    /// <summary>
    /// Returns true if a directory with the same name (ignoring case) exists
    /// </summary>
    /// <param name="dict"></param>
    /// <param name="directory"></param>
    /// <returns></returns>
    private bool DuplicatePathExists(Dictionary<string, object> dict, string path)
    {
        List<string> dirs = SplitDirectory(path);
        Dictionary<string, object> currentDict = dict;

        int index = 0;
        foreach (string dir in dirs)
        {
            if (currentDict.ContainsKey(dir))
            {
                if (index == dirs.Count - 1)
                {
                    foreach (string key in currentDict.Keys)
                        if (string.Compare(key, dir, true) == 0 && key != dir) return true;
                }

                currentDict = (Dictionary<string, object>)currentDict[dir];
            }

            ++index;
        }

        return false;
    }

    private string GetDuplicateDirectoryName(Dictionary<string, object> dict, string directory)
    {
        List<string> dirs = SplitDirectory(directory);
        Dictionary<string, object> currentDict = dict;

        string duplicateDir = string.Empty;

        int index = 0;
        foreach (string dir in dirs)
        {
            if (currentDict.ContainsKey(dir))
            {
                if (index == dirs.Count - 1)
                {
                    foreach (string key in currentDict.Keys)
                        if (string.Compare(key, dir, true) == 0 && key != dir)
                        {
                            return duplicateDir + key;
                        }
                }

                duplicateDir = duplicateDir + dir + "\\";

                currentDict = (Dictionary<string, object>)currentDict[dir];
            }

            ++index;
        }

        return string.Empty;

    }

    private string GetDuplicatePathName(Dictionary<string, object> dict, string directory)
    {
        List<string> dirs = SplitDirectory(directory);
        Dictionary<string, object> currentDict = dict;

        string duplicateDir = string.Empty;

        int index = 0;
        foreach (string dir in dirs)
        {
            if (currentDict.ContainsKey(dir))
            {
                if (index == dirs.Count - 1)
                {
                    foreach (string key in currentDict.Keys)
                        if (string.Compare(key, dir, true) == 0 && key != dir)
                        {
                            return duplicateDir + key;
                        }
                }

                duplicateDir = duplicateDir + dir + "\\";

                currentDict = (Dictionary<string, object>)currentDict[dir];
            }

            ++index;
        }

        return string.Empty;

    }

    /// <summary>
    /// Split a directory into subdirectories
    /// </summary>
    /// <param name="relativeDirectory"></param>
    /// <returns></returns>
    private List<string> SplitDirectory(string relativeDirectory)
    {
        List<string> split = new List<string>();

        string[] dirs = relativeDirectory.Split(new char[] { '/' });

        return dirs.ToList<string>();
    }

    #endregion

    #endregion

    #region Event handlers

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
      // Create our TFS Module
      _tfsMod = new TFSModule();

      // Populate the dropdowns
      _cboTfsCollection.ItemsSource = _tfsMod.GetOnlineCollections();
      _cboTfsCollection.SelectedIndex = 0;
    }

    private void _cboTfsCollection_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
      if (e == null || e.AddedItems == null || e.AddedItems.Count <= 0 || e.AddedItems[0] == null || !(e.AddedItems[0] is string))
      {
        return;
      }

      _cboTfsProject.ItemsSource = _tfsMod.GetTeamProjects((string)e.AddedItems[0]);
      _cboTfsProject.SelectedIndex = 0;
    }

    private void _cboTfsProject_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e == null || e.AddedItems == null || e.AddedItems.Count <= 0 || e.AddedItems[0] == null || !(e.AddedItems[0] is string))
        {
            return;
        }

        _cboProjectBranch.ItemsSource = _tfsMod.GetBranches((string)e.AddedItems[0], (string)_cboTfsCollection.SelectedItem);
        _cboProjectBranch.SelectedIndex = 0;
    }

    #endregion

  }
}
